/** TemplateBasedShadowElement.java.

	Purpose:
		
	Description:
		
	History:
		4:15:24 PM Nov 12, 2014, Created by jumperchen

Copyright (C) 2014 Potix Corporation. All Rights Reserved.
 */
package org.zkoss.zuti.zul;

import java.util.Iterator;
import java.util.List;

import org.zkoss.bind.BindContext;
import org.zkoss.bind.sys.BinderCtrl;
import org.zkoss.zk.ui.Component;
import org.zkoss.zk.ui.Executions;
import org.zkoss.zk.ui.HtmlShadowElement;
import org.zkoss.zk.ui.event.Event;
import org.zkoss.zk.ui.event.EventListener;
import org.zkoss.zk.ui.event.Events;
import org.zkoss.zk.ui.sys.ComponentCtrl;
import org.zkoss.zk.ui.util.Template;

/**
 * A template based skeleton class
 * 
 * @author jumperchen
 * @since 8.0.0
 */
public abstract class TemplateBasedShadowElement extends HtmlShadowElement {
	/**
	 * Component developer use only
	 */
	public static String ON_BINDING_READY = "onBindingReady";
	/**
	 * Component developer use only
	 */
	public static String SHADOW_VARIABLE = "$ShadowVariable$";

	/**
	 * foreach support list model (Component developer use only)
	 */
	public static final String FOREACH_RENDERED_COMPONENTS = "$ForEachRenderedComponents$";

	private boolean _bindingReady = false;

	protected boolean _dirtyBinding = true; // initial case is true

	public TemplateBasedShadowElement() {
		init();
	}

	private void init() {
		addBindingListener();
	}

	public void onChildRemoved(Component child) {
		if (child instanceof TemplateBasedShadowElement)
			((TemplateBasedShadowElement) child).removeBindingListener();
		super.onChildRemoved(child);
	}

	public void onChildAdded(Component child) {
		if (child instanceof TemplateBasedShadowElement)
			((TemplateBasedShadowElement) child).addBindingListener();
		super.onChildAdded(child);
	}

	public void afterCompose() {
		docheck0();
		if (!_afterComposed) { // don't do it twice, if it has a child.
			if (getFirstInsertion() == null) {
				// call super to do
				super.afterCompose();

				if (getFirstInsertion() != null) {
					List<Component> distributedChildren = getDistributedChildren();

					// support mvvm
					if (_bindingReady) {
						for (Component comp : distributedChildren)
							Events.sendEvent(new Event(BinderCtrl.ON_BIND_INIT, comp));

						// send with current shadow element if sub-shadow
						// element contains @ annotation
						for (Component shadow : getChildren())
							Events.sendEvent(new Event(BinderCtrl.ON_BIND_INIT, shadow));
					}
				}
			}
			_afterComposed = true; // just in case
		}
	}

	public boolean isDynamicValue() {

		// F80-ZK-2950
		if (_dynamicValue != null && _dynamicValue.booleanValue())
			return true; // it's in a persistent state

		docheck0();
		if (getParent() instanceof ForEach) {
			if (hasBindingAnnotation() || hasSubBindingAnnotation()) {
				return true;
			} else {
				Component next = getFirstInsertion();
				while (next != null) {
					ComponentCtrl nextCtrl = ((ComponentCtrl) next);
					if (nextCtrl.hasBindingAnnotation() || nextCtrl.hasSubBindingAnnotation())
						return true;
					if (next == getLastInsertion())
						break;
					next = next.getNextSibling();
				}
			}
		}
		return super.isDynamicValue();
	}

	// open for different java package.
	protected void rebuildSubShadowTree() {
		docheck0();
		super.rebuildSubShadowTree();
	}

	protected void compose(Component host) {
		docheck0();
		Template t = getTemplate("");
		if (t != null) {
			t.create(host, getNextInsertionComponentIfAny(), null, null);
		}
	}

	protected void addBindingListener() {
		if (!isListenerAvailable(ON_BINDING_READY, false)) {
			addEventListener(ON_BINDING_READY, new EventListener<Event>() {
				public void onEvent(Event event) throws Exception {
					_bindingReady = true; // for sub-class to check

					// remove the cache flag.
					if (event.getData() instanceof BindContext) {
						BindContext ctx = (BindContext) event.getData();
						String k = (String) ctx.getAttribute(event.getTarget().getUuid());
						Executions.getCurrent().removeAttribute(k);
					}

					// optimized for unnecessary change
					if (!_dirtyBinding) {
						return;
					}

					_dirtyBinding = false;

					// the host may be an orphan and we cannot scan all of its children to cleanup
					final Component host = getShadowHostIfAny();
					if (host != null && host.getDesktop() != null) {
						recreate();
					} else {
						removeBindingListener();
					}
				}
			});
		}
	}

	protected void removeBindingListener() {
		Iterable<EventListener<? extends Event>> eventListeners = getEventListeners(ON_BINDING_READY);
		for (Iterator<EventListener<? extends Event>> it = eventListeners.iterator(); it.hasNext();)
			it.remove();
	}

	public void detach() {
		docheck0();
		removeBindingListener();
		super.detach();
	}

	/**
	 * Returns the current phase is in a binding ready state, if any. It is used
	 * for sub-class to check the state in a {@link #recreate()} method.
	 */
	protected boolean isBindingReady() {
		docheck0();
		return _bindingReady;
	}

	protected boolean isEffective() {
		return false;
	}

	@SuppressWarnings("rawtypes")
	public Object clone() {
		final TemplateBasedShadowElement clone = (TemplateBasedShadowElement) super.clone();
		clone.init();
		return clone;
	}

	@SuppressWarnings("rawtypes")
	private void readObject(java.io.ObjectInputStream s) throws java.io.IOException, ClassNotFoundException {
		s.defaultReadObject();
		init();
	}

	protected void docheck0() {
		org.zkoss.zkex.rt.Runtime.init(this);
	}
}
